package ar.fi.uba.celdas.ambiente;

import java.awt.Point;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Stack;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author GrupoCeldas
 *
 */
public class Laberinto2D extends Laberinto {

	private static final Log log = LogFactory.getLog(Laberinto2D.class);
	private int width;
	private int height;
	
	private Point start;
	private Point finish;
	
	private Random generator;
	
	private TIPO_CUBO matriz2D[][];
	
	/**
	 * Constructor que utiliza el tiempo actual para incializar el generador aleatorio interno
	 * 
	 * @param widthseed  semilla para calcular el ancho final del laberinto (2x + 1)
	 * @param heightseed  semilla para calcular la altura final del laberinto (2x + 1)
	 */
	public Laberinto2D (int widthseed, int heightseed) {
		
		this(widthseed, heightseed, System.currentTimeMillis());
	}
	
	/**
	 * Constructor que recibe semilla para incializar el generador aleatorio interno
	 * 
	 * @param widthseed  semilla para calcular el ancho final del laberinto (2x + 1)
	 * @param heightseed  semilla para calcular la altura final del laberinto (2x + 1)
	 * @param generatorSeed semilla para el generador de numeros aleatorios
	 */
	public Laberinto2D (int widthseed, int heightseed, long generatorSeed) {
		
		this.width = (widthseed * 2) + 1;
		this.height = (heightseed * 2) + 1;
		this.start = new Point();
		this.finish = new Point();
		this.generator = new Random(generatorSeed);
	}
	
	@Override
	public TIPO_CUBO getCubo(int x, int y) {

		return this.matriz2D[y][x];
	}

	@Override
	public Point getTamanio() {

		return new Point(this.width, this.height);
	}

	@Override
	public Point getEntrada() {

		return this.start;
	}

	@Override
	public Point getSalida() {

		return this.finish;
	}

	private int generarRandomImpar(int limit) {
		
		return ( ((int) (this.generator.nextDouble() * limit)) * 2) + 1;
	}
	
	@Override
	public void generarLaberinto() {
		
		int hseed = (this.height - 1) / 2;
		int wseed = (this.width - 1) / 2;
		
		int totalLugares = hseed * wseed;
		int lugaresUsados = 1;
		int randomX = generarRandomImpar(wseed);
		int randomY = generarRandomImpar(hseed);
		Point bloqueActual = new Point (randomX, randomY);
		this.start.setLocation(randomX, randomY);
		
		this.matriz2D = new TIPO_CUBO[this.height][this.width];
		inicializarMatriz();
		printMatrix("matrizInicial.txt");
		Stack<Point> pilaBloques = new Stack<Point>();
		
		while ( (randomX == this.start.x) && (randomY == this.start.y) ) {
			randomX = generarRandomImpar(wseed);
			randomY = generarRandomImpar(hseed);
		}
		this.finish.setLocation(randomX, randomY);

		StringBuilder str = new StringBuilder(); 
		String ls = System.getProperty("line.separator");

		str.append("height: ").append(this.height).append(ls)
			.append("width: ").append(this.width).append(ls)
			.append("hseed: ").append(hseed).append(ls)
			.append("wseed: ").append(wseed).append(ls)
			.append("TotalLugares: ").append( totalLugares).append(ls)
			.append("start x: ").append(this.start.x).append(", y: " ).append(this.start.y).append(ls)
			.append("finish x: ").append( this.finish.x).append(", y: ").append(this.finish.y);		
		log.debug(str.toString());
		
		while (lugaresUsados < totalLugares) {
			List<Point> vecinosIntactos = buscarVecinosIntactos(bloqueActual.x, bloqueActual.y);
			int size = vecinosIntactos.size();
			if (size > 0) {
				// se elige un vecino al azar
				Point vecino = vecinosIntactos.get((int) (this.generator.nextDouble() * size));
				derribarParedConjunta(bloqueActual, vecino);
				pilaBloques.push(bloqueActual);
				bloqueActual = vecino;
				lugaresUsados++;
			} else {
				bloqueActual = pilaBloques.pop();
			}
		}
		
	}
	
	private void inicializarMatriz() {
		
		for (int y = 0; y < this.height; y++) {
			for (int x = 0; x < this.width; x++) {
				this.matriz2D[y][x] = TIPO_CUBO.OBSTACULO;
			}
		}
		
		for (int y = 1; y < this.height; y += 2) {
			for (int x = 1; x < this.width; x  += 2) {
				this.matriz2D[y][x] = TIPO_CUBO.LIBRE;
			}
		}
	}
	
	private List<Point> buscarVecinosIntactos(int posx, int posy) {
		
		List<Point> vecinosIntactos = new ArrayList<Point>(4);
		
		// Oeste
		if ( (posx - 2) > 0 ) {
			if (tieneTodasParedesIntactas(posx - 2, posy)) {
				vecinosIntactos.add(new Point(posx - 2, posy));
			}
		}
		
		// Este
		if ( (posx + 2) < this.width ) {
			if (tieneTodasParedesIntactas(posx + 2, posy)) {
				vecinosIntactos.add(new Point(posx + 2, posy));
			}
		}

		// Norte
		if ( (posy - 2) > 0 ) {
			if (tieneTodasParedesIntactas(posx, posy - 2)) {
				vecinosIntactos.add(new Point(posx, posy - 2));
			}
		}
		
		// Sur
		if ( (posy + 2) < this.height ) {
			if (tieneTodasParedesIntactas(posx, posy + 2)) {
				vecinosIntactos.add(new Point(posx, posy + 2));
			}
		}
		
		return vecinosIntactos;
	}

	private boolean tieneTodasParedesIntactas(int posx, int posy) {
		
		boolean res = true;
		
		if ( (this.matriz2D[posy - 1][posx] == TIPO_CUBO.LIBRE) || 
			(this.matriz2D[posy + 1][posx] == TIPO_CUBO.LIBRE) ||
			(this.matriz2D[posy][posx - 1] == TIPO_CUBO.LIBRE) ||
			(this.matriz2D[posy][posx + 1] == TIPO_CUBO.LIBRE) ) {
				res = false;
		}
		
		return res;
	}
	
	private void derribarParedConjunta(Point actual, Point vecino) {
		
		int posX = (actual.x + vecino.x) / 2;
		int posY = (actual.y + vecino.y) / 2;
		
		this.matriz2D[posY][posX] = TIPO_CUBO.LIBRE;
	}
	
	// TODO SOLO PARA PRUEBAS
	/**
	 * Imprime la matriz laberinto en forma de 0s (OBSTACULO) y 1s (LIBRE)
	 * 
	 * @param nombre
	 */
	public void printMatrix(String nombre) {
		
		try {
			FileWriter fstream = new FileWriter(nombre);
			BufferedWriter out = new BufferedWriter(fstream);
			for (int y = 0; y < this.height; y++) {
				for (int x = 0; x < this.width; x++) {
					if (this.matriz2D[y][x] == TIPO_CUBO.OBSTACULO) {
						out.write("0");
					} else {
						out.write("1");
					}
				}
				out.newLine();
			}
			out.close();
		} catch (Exception e) {
			System.err.println("Error: " + e.getMessage());
		}

	}
	
	
}
